
// --------------------

function EventoUserProfile(simulador, profile, dt) {
	this.simulador = simulador;
	this.profile = profile;
	this.dt = dt;
	return this;
}
EventoUserProfile.prototype = {
	run: function() {
		// Realiza amostragem.
		this.profile.add_sample();

		// Agenda próxima amostragem.
		this.simulador.add_evento(this.dt, this);

		return false;
	}
}

// --------------------

function EventoUserUpload(simulador, user) {
	this.simulador = simulador;
	this.user = user;
	return this;
}
EventoUserUpload.prototype = {
	escolhe_peer: function() {
		if (this.user.classe == User.CLASSES.PEER) {
			// Se eu sou um dos peers, não posso me selecionar.
			var n = this.simulador.peers_ativos.length - 1;
			if (n == 0) return undefined;

			var choice = Math.floor(Math.random() * n);
			var idx = this.simulador.peers_ativos.indexOf(this.user);
			if (idx <= choice) {
				return this.simulador.peers_ativos[choice+1];
			} else {
				return this.simulador.peers_ativos[choice];
			}

		} else {
			// Se eu não sou peer, posso simplesmente escolher
			// um aleatoriamente.
			var n = this.simulador.peers_ativos.length;
			if (n == 0) return undefined;

			var choice = Math.floor(Math.random() * n);
			return this.simulador.peers_ativos[choice];
		}
	},
	escolhe_bloco: function(peer) {
		var candidatos = [];
		var choice;
		var bloco;
		for(var i=0; i<this.simulador.settings.b; i++) {
			if (this.user.blocos[i] && !peer.blocos[i]) {
				candidatos.push(i);
			}
		}
		if (candidatos.length == 0) {
			return undefined;
		}
		if (this.simulador.settings.politica_escolha_bloco == Simulador.ALEATORIO) {
			choice = Math.floor(Math.random() * candidatos.length);
			bloco = candidatos[choice];
		} else if (this.simulador.settings.politica_escolha_bloco == Simulador.RARO) {
			bloco = this.simulador.bloco_mais_raro(candidatos);
		}
		return bloco;
	},
	run: function() {
		if (!this.user.ativo) {
			//console.log("Opz! Seed já saiu e não pode fazer upload.");
			return false;
		}
		this.simulador.log('EventoUserUpload running.');
		var enviou_bloco = false;

		// Faz upload para um peer.
		var peer = this.escolhe_peer();
		if (peer != undefined) {
			var bloco = this.escolhe_bloco(peer);
			if (bloco != undefined) {
				var qtde_blocos = 0;
				for (var i=0; i<this.simulador.settings.b; i++) {
					if (peer.blocos[i] == true) {
						qtde_blocos++;
					}
				}
				if (qtde_blocos == 0) {
					// Se for o primeiro bloco, agenda upload.
					peer.agenda_upload();
				}
				this.simulador.log(this.user.nome+' -> '+peer.nome+' [bloco '+bloco+']');
				peer.blocos[bloco] = true;

				this.simulador.qtd_peers_blocos[bloco]++;
				this.simulador.blocos_enviados++;
				this.user.blocos_enviados++;

				enviou_bloco = true;
				if (qtde_blocos+1 == this.simulador.settings.b) {
					// Peer vira seed.
					var idx = this.simulador.peers_ativos.indexOf(peer);
					this.simulador.peers_ativos.splice(idx, 1);
					this.simulador.seeds_ativos.push(peer);
					peer.virou_seed.rodada = this.simulador.rodada_atual;
					peer.virou_seed.tempo = this.simulador.tempo_atual;
					peer.classe = User.CLASSES.SEED;
					this.simulador.seeds_download_agora.push(peer);
					// Agenda saída.
					peer.agenda_saida();
				}
			} else {
				this.simulador.blocos_nao_enviados_bloco++;
			}
		} else {
			this.simulador.blocos_nao_enviados_peer++;
		}
		this.user.blocos_nao_enviados++;


		// Agenda próximo evento de upload
		this.user.agenda_upload();
		return true;
	}
}

// --------------------

function EventoSeedSaida(simulador, seed) {
	this.simulador = simulador;
	this.seed = seed;
	return this;
}
EventoSeedSaida.prototype = {
	run: function() {
		if (!this.seed.ativo) {
			//console.log("Opz! Seed já saiu e não pode sair.");
			return false;
		}
		this.simulador.log('EventoSeedSaida running: '+this.seed.nome);

		// Profile.
		this.seed.saida.tempo = this.simulador.tempo_atual;
		this.seed.saida.rodada = this.simulador.rodada_atual;

		// Move para a lista dos seeds inativos.
		var idx = this.simulador.seeds_ativos.indexOf(this.seed);
		this.simulador.seeds_ativos.splice(idx, 1);
		this.simulador.seeds_inativos.push(this.seed);

		// Desativa peer.
		// É utilizado para que seus eventos agendados sejam ignorados.
		this.seed.ativo = false;

		// Utilizado para o cálculo da vazão de peers.
		this.seed.saida.nr_peers_ativos = this.simulador.peers_ativos.length;

		// Recomenda novo peer com probabilidade P de aceitação.
		if (Math.random() < this.simulador.settings.p) {
			var nome = 'R'+this.seed.nome;
			this.simulador.add_peer_ativo(nome);
		}

		for(var i=0; i<this.simulador.settings.b; i++) {
			this.simulador.qtd_peers_blocos[i]--;
		}

		return true;
	}
}

// --------------------

var nr_chegada_externa = 0;

function EventoPeerChegadaExterna(simulador) {
	this.simulador = simulador;

	return this;
}
EventoPeerChegadaExterna.nr_chegadas_externas = 0;
EventoPeerChegadaExterna.prototype = {
	run: function() {
		this.simulador.log('EventoPeerChegadaExterna running.');
		nr_chegada_externa++;

		// Instancia novo peer externo e adiciona ao simulador.
		EventoPeerChegadaExterna.nr_chegadas_externas++;
		var nome = 'E'+EventoPeerChegadaExterna.nr_chegadas_externas;
		this.simulador.add_peer_ativo(nome);

		// Agenda próximo evento de chegada de peer externo.
		var dt = expovariate(this.simulador.settings.lambda);
		this.simulador.add_evento(dt, new EventoPeerChegadaExterna(this.simulador));
		return true;
	}
}

